package one.beex.robot.client;

import io.broker.api.client.BrokerApiClientFactory;
import io.broker.api.client.BrokerApiWebSocketClient;
import io.broker.api.client.constant.BrokerConstants;
import io.broker.api.client.domain.account.NewOrderResponse;
import io.broker.api.client.domain.account.Order;
import io.broker.api.client.domain.account.OrderSide;
import io.broker.api.client.domain.event.DepthEvent;
import io.broker.api.client.service.BrokerApiCustomService;
import one.beex.robot.log.LoggerUtil;
import one.beex.robot.symbol.SymbolInfo;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class RobotClient implements Runnable {

    private final RobotConfig robotConfig;

    private final BrokerApiCustomService brokerApiCustomService;

    private final SymbolInfo symbolInfo;

    private final BigDecimal inputVolume;

    private final BigDecimal inputPrice;

    private final String symbol;

    private volatile DepthEvent marketDepth;

    public RobotClient(
            String symbol,
            BigDecimal inputPrice,
            BigDecimal inputVolume,
            RobotConfig robotConfig,
            BrokerApiCustomService brokerApiCustomService,
            SymbolInfo symbolInfo) {
        this.symbol = symbol;
        this.inputPrice = inputPrice;
        this.inputVolume = inputVolume;
        this.robotConfig = robotConfig;
        this.brokerApiCustomService = brokerApiCustomService;
        this.symbolInfo = symbolInfo;

        BrokerApiWebSocketClient brokerApiWebSocketClient = BrokerApiClientFactory.newInstance(BrokerConstants.API_BASE_URL)
                .newWebSocketClient(
                        BrokerConstants.WS_API_BASE_URL, BrokerConstants.WS_API_USER_URL);
        brokerApiWebSocketClient.onDepthEvent(symbol, response -> marketDepth = response, true);
    }

    private void doTask() {

        if (marketDepth == null
                || CollectionUtils.isEmpty(marketDepth.getAsks())
                || CollectionUtils.isEmpty(marketDepth.getBids())) {
            return;
        }

        boolean con1 =
                RobotKit.getRandom(BigDecimal.valueOf(0), BigDecimal.valueOf(1), 8)
                                .compareTo(
                                        BigDecimal.valueOf(1)
                                                .subtract(
                                                        robotConfig
                                                                .getTimesBetween7min()
                                                                .divide(
                                                                        BigDecimal.valueOf(420),
                                                                        8,
                                                                        RoundingMode.FLOOR)))
                        > 0;
        BigDecimal aksPrice = new BigDecimal(marketDepth.getAsks().get(0).getPrice());
        BigDecimal bidPrice = new BigDecimal(marketDepth.getBids().get(0).getPrice());

        int scale = symbolInfo.getMinPrice().scale();

        boolean con2 =
                aksPrice.subtract(bidPrice)
                                .divide(bidPrice, 8, RoundingMode.FLOOR)
                                .compareTo(BigDecimal.valueOf(0.3))
                        <= 0;

        boolean con3 =
                aksPrice.divide(bidPrice, scale, RoundingMode.FLOOR)
                                .compareTo(BigDecimal.valueOf(5).multiply(symbolInfo.getMinPrice()))
                        >= 0;

        boolean con4 = aksPrice.subtract(bidPrice).compareTo(symbolInfo.getMinPrice()) > 0;

        if (con1 && con2 && con3 && con4) {
            BigDecimal inputVolume_;
            BigDecimal inputPrice_;

            if (inputVolume == null) {
                inputVolume_ =
                        RobotKit.getInputVolume(
                                robotConfig.getVolumeBetween7min(),
                                robotConfig.getTimesBetween7min(),
                                symbolInfo);
            } else {
                inputVolume_ = inputVolume;
            }

            if (inputPrice == null) {
                inputPrice_ = RobotKit.getInputPrice(marketDepth, symbolInfo);
            } else {
                inputPrice_ = inputPrice;
            }

            NewOrderResponse orderBuy = null;

            NewOrderResponse orderSell = null;

            try {
                orderBuy =
                        brokerApiCustomService.newOrder(
                                symbol, OrderSide.BUY, inputVolume_, inputPrice_);
                orderSell =
                        brokerApiCustomService.newOrder(
                                symbol, OrderSide.SELL, inputVolume_, inputPrice_);

                LoggerUtil.info("OrderSell({})", orderSell);
                LoggerUtil.info("OrderBuy({})", orderBuy);
            } catch (Exception ex) {
                ex.printStackTrace();
            }

            try {
                boolean retry = true;
                while (retry) {
                    retry = false;
                    List<Order> openOrders;
                    if (!CollectionUtils.isEmpty(openOrders = brokerApiCustomService.getOpenOrders())) {
                        for (Order openOrder : openOrders) {
                            assert orderBuy != null;
                            assert orderSell != null;
                            if (openOrder.getOrderId().equals(orderBuy.getOrderId())
                                    || openOrder.getOrderId().equals(orderSell.getOrderId())) {
                                LoggerUtil.info("open order:{}", openOrder);
                                retry = true;
                                sleep(6000L);
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    private void sleep(Long millis) {
        try {
            TimeUnit.MILLISECONDS.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            while (true) {
                doTask();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
